﻿Imports System.Windows.Interop
Imports System.Windows.Forms
Imports Microsoft.Windows.Controls
Imports Microsoft.Windows.Controls.Primitives
Imports System.Windows.Threading
Imports System.Text.RegularExpressions
Imports System.Reflection
Imports Co9320.NamaAlert.Model
Imports Co9320.NamaAlert.Engine
Imports System.Threading
Imports Co9320.NamaAlert
Imports System.Net
Imports Co9320.NamaAlert.Contract
Imports System.Web

<ServiceModel.ServiceBehavior(InstanceContextMode:=ServiceModel.InstanceContextMode.Single)> _
Class MainWindow
    Implements Contract.IApplicationService

    Private WithEvents TrayIcon As New Forms.NotifyIcon
    Private WithEvents Crawler As New Crawler
    Private IsClosed As Boolean
    Private Settings As New UserSettings
    Private AlertManager As AlertManager

    Private WithEvents ConfigClient As ServiceReference.ConfigSoapClient

    Private NgStationIds As New List(Of String)

    Private WithEvents LiveCommentServer As LiveCommentServer
    Private WithEvents LiveNotification As LiveNotification

    Private WindowRect As New Rect


#Region "Commands Declare"

    Public Shared ReadOnly AddStationsCommand As RoutedCommand = New RoutedCommand("AddStations", GetType(MainWindow))
    Public Shared ReadOnly DeleteStationsCommand As RoutedCommand = New RoutedCommand("DeleteStations", GetType(MainWindow))
    Public Shared ReadOnly DeleteKeywordsCommand As RoutedCommand = New RoutedCommand("DeleteKeywords", GetType(MainWindow))
    Public Shared ReadOnly OpenUrisCommand As RoutedCommand = New RoutedCommand("OpenUris", GetType(MainWindow))
    Public Shared ReadOnly CopyUriCommand As RoutedCommand = New RoutedCommand("CopyUri", GetType(MainWindow))
    'Public Shared ReadOnly AddToNgCommand As RoutedCommand = New RoutedCommand("AddToNg", GetType(MainWindow))
    'Public Shared ReadOnly ShowGeneralSettingsCommand As RoutedCommand = New RoutedCommand("ShowGeneralSettings", GetType(MainWindow))
    Public Shared ReadOnly ShowAlertSettingsCommand As RoutedCommand = New RoutedCommand("ShowAlertSettings", GetType(MainWindow))
    Public Shared ReadOnly FilterProgramsCommand As RoutedCommand = New RoutedCommand("FilterPrograms", GetType(MainWindow))

    'Public Shared ReadOnly SetCrawlingIntervalCommand As RoutedCommand = New RoutedCommand("SetCrawlingInterval", GetType(MainWindow))
    'Public Shared ReadOnly EnableCrawlingDetailsCommand As RoutedCommand = New RoutedCommand("EnableCrawlingDetails", GetType(MainWindow))
    Public Shared ReadOnly SetTimerCommand As RoutedCommand = New RoutedCommand("SetTimer", GetType(MainWindow))
    Public Shared ReadOnly OpenNiconamaCommentViewerCommand As RoutedCommand = New RoutedCommand("OpenNiconamaCommentViewer", GetType(MainWindow))

#End Region

    Private ReadOnly Property Context() As NamaAlertDataContext
        Get
            Return DirectCast(Me.DataContext, NamaAlertDataContext)
        End Get
    End Property

    Public Sub New()
        InitializeComponent()
        NotificationTimer.Interval = New TimeSpan(0, 5, 0)


    End Sub


#Region "Me"

    ' MEMO 起動時タスクトレイ格納のため別メソッドに移動させた
    Public Sub Start()

        ' Tray
        InitializeTray()

        Station.SetCachePath(System.IO.Path.Combine(Me.UserDataPath, "cache"))

        ' DataContext
        Dim dataContext = New NamaAlertDataContext()
        dataContext.SetUserSettings(Settings)
        Me.DataContext = dataContext

        ProgramDataGrid.ItemsSource = CollectionViewSource.GetDefaultView(Me.Context.LivePrograms)
        StationDataGrid.ItemsSource = CollectionViewSource.GetDefaultView(Me.Context.Stations)

        Me.Load()

        ' Alert Manager
        AlertManager = New AlertManager(Settings)

        ' Status Bar
        AddStatusBarText(My.Resources.ApplicationName & " Ver. " & My.Resources.Version)

        ' ProgramDataGrid Filter
        Dim l = DirectCast(ProgramDataGrid.ItemsSource, ListCollectionView)
        l.Filter = New Predicate(Of Object)(AddressOf FilterProgram)

        Try
            If My.Settings.ReadConfig Then
                ConfigClient = New ServiceReference.ConfigSoapClient
                ConfigClient.GetSettingsAsync(My.Resources.Version)
            Else
                InitializeNotification()
            End If
        Catch ex As Exception
            '
        End Try

        If Me.Context.Settings.General.IsInTray Then
            'Debug.WriteLine("MainWindow_Loaded")
            NotificationTimer.Start()
        End If

        Try
            ApplicationServer.Open(Me)
        Catch ex As Exception

        End Try

    End Sub

    Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim source = DirectCast(PresentationSource.FromDependencyObject(Me), HwndSource)
        source.AddHook(New HwndSourceHook(AddressOf WindowProc))

    End Sub

    Private Function WindowProc(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
        Select Case msg
            Case &H11, &H16
                ' Windows Shut Down
                Me.Save()
            Case &H112
                If (wParam.ToInt32 And &HFFF0) = &HF060 Then
                    ' UserClosing
                    If Not IsClosed Then
                        Me.Hide()
                        Me.Context.Settings.General.IsInTray = True
                        handled = True

                        Debug.WriteLine("WindowProc")
                        NotificationTimer.Start()

                    Else
                        Me.Save()
                    End If
                End If
        End Select

        Return IntPtr.Zero

    End Function

#End Region

#Region "Live Notification"

    Private Sub InitializeNotification()

        LiveNotification = New LiveNotification(My.Settings.UserAgent, My.Settings.LiveProgramsFeedUriFormat, New TimeSpan(0, 0, 3))
        LiveCommentServer = LiveCommentServer.CreateAnonymousSession(My.Settings.UserAgent)

        For Each s In Me.Context.Stations
            LiveCommentServer.RegisterNotifiedStation(s.Id)
        Next
        LiveCommentServer.Start()

        LiveNotification.Start(CrawlingMode.Normal)

    End Sub

    Private Sub StartedLiveProgramProcedure(ByVal program As Model.LiveProgram)

        Dim pid = "lv" & program.Id.ToString

        ' 通知済みの場合
        Try

            Dim existPrograms = From p In Me.Context.LivePrograms Where p.Id = pid


            If existPrograms.Count > 0 Then

                ' 情報の更新
                With existPrograms.First

                    .VisitorCount = If(program.VisitorCount > .VisitorCount, program.VisitorCount, .VisitorCount)
                    .CommentCount = If(program.CommentCount > .CommentCount, program.CommentCount, .CommentCount)
                    .Title = If(program.Title.Length < .Title.Length, program.Title, .Title)
                    .Caster = If(program.CasterName <> "", program.CasterName, .Caster)
                    .CrawlDateTime = program.CrawlingDateTime

                    .IsMemberOnly = program.IsMemberOnly
                    .IsFace = program.IsFace
                    .Category = program.OfficialTag

                End With
                Me.Resort(existPrograms.First)
                Exit Sub
            End If

        Catch ex As Exception
            Debug.Assert(False)
        Finally
        End Try

        ' NG
        If NgStationIds.Contains(program.Station.Id) Then
            Exit Sub
        End If

        ' 新規放送
        Dim newProgram = New LiveProgram
        With newProgram
            .Id = pid
            .Title = If(program.Title <> "", program.Title, .Title)
            .Content = If(program.Content <> "", program.Content, .Content)
            .Caster = If(program.CasterName IsNot Nothing, program.CasterName, .Caster)
            .CommentCount = If(program.CommentCount > .CommentCount, program.CommentCount, .CommentCount)
            .VisitorCount = If(program.VisitorCount > .VisitorCount, program.VisitorCount, .VisitorCount)
            .IsMemberOnly = If(program.IsMemberOnly, program.IsMemberOnly, .IsMemberOnly)
            .IsFace = If(program.IsFace, program.IsFace, .IsFace)
            .StartDateTime = program.StartDateTime
            .Category = If(program.OfficialTag <> "", program.OfficialTag, .Category)
            .CrawlDateTime = program.CrawlingDateTime

            .Station = New Station
            .Station.Id = program.Station.Id
            .Station.Name = program.Station.Name
            .Station.RecentAirdate = program.StartDateTime
            .Station.RecentTitle = program.Title
            .Station.IsOnAir = True
            .Station.IconUri = program.Station.IconUri
        End With

        ' 復帰時 IsAlert 設定 仮対応
        Dim alreadyAlerted As Boolean = False
        If AlertManager.AlertedIds.Contains(pid) Then
            newProgram.IsAlerted = True
            alreadyAlerted = True
        End If

        Me.Context.LivePrograms.Add(newProgram)

        ' Station
        Dim isOnAir As Boolean
        Dim sid = program.Station.Id
        Try


            Dim existStation = From s In Me.Context.Stations Where s.Id = sid
            If existStation.Count > 0 _
                AndAlso existStation.First.RecentAirdate <= program.StartDateTime.AddMinutes(5) Then


                ' MEMO RecentAirdate には、LiveCommentServer により クライアントPCの時刻が設定されている場合がある
                ' 情報の更新
                With existStation.First
                    .Name = program.Station.Name
                    .RecentAirdate = program.StartDateTime
                    .RecentTitle = program.Title
                    .IsOnAir = True
                End With

                isOnAir = True
            End If

        Catch ex As Exception
            Debug.Assert(False)

        Finally
        End Try

        If isOnAir Then ' とりあえず再ソート対応
            Try
                CollectionViewSource.GetDefaultView(Me.Context.Stations).Refresh()
            Catch ex As InvalidOperationException
            End Try
        End If

        ' Alert
        Try
            If AlertManager.Alert(Me.Context.LivePrograms) Then
                LiveCommentServer.RegisterNotifiedStation(sid)
                Try
                    CollectionViewSource.GetDefaultView(Me.Context.LivePrograms).Refresh()
                Catch ex As Exception
                End Try
                ChangeTrayStatus()
            ElseIf alreadyAlerted Then
                Try
                    ' 通知数更新
                    Me.Context.AlertedStationsCount = (From s In Me.Context.Stations Where s.IsOnAir).Count
                Catch ex As Exception
                End Try
            End If
        Catch ex As Exception
            AddStatusBarText(String.Format("{0} {1} [01]", Now.ToString("HH:mm"), ex.ToString))
        End Try

    End Sub

    Private Sub LiveCommentServer_LiveProgramStarted(ByVal sender As Object, ByVal e As NamaAlert.Model.LiveProgramEventArgs) Handles LiveCommentServer.LiveProgramStarted
        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, LiveProgramEventArgs)(AddressOf LiveCommentServer_LiveProgramStarted), DispatcherPriority.Background, New Object() {sender, e})
            Exit Sub
        End If

        LiveNotification.AddProgram(DirectCast(e.LiveProgram.Clone, Co9320.NamaAlert.Model.LiveProgram))

        StartedLiveProgramProcedure(e.LiveProgram)
    End Sub

    Private Sub LiveNotification_LiveProgramStarted(ByVal sender As Object, ByVal e As NamaAlert.Model.LiveProgramEventArgs) Handles LiveNotification.LiveProgramStarted

        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, LiveProgramEventArgs)(AddressOf LiveNotification_LiveProgramStarted), DispatcherPriority.Background, New Object() {sender, e})
            Exit Sub
        End If

        StartedLiveProgramProcedure(e.LiveProgram)
    End Sub

    Private Sub LiveNotification_LiveProgramClosed(ByVal sender As Object, ByVal e As NamaAlert.Model.LiveProgramEventArgs) Handles LiveNotification.LiveProgramClosed

        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, LiveProgramEventArgs)(AddressOf LiveNotification_LiveProgramClosed), DispatcherPriority.Background, New Object() {sender, e})
            Exit Sub
        End If

        ' 放送終了している通知対象のOnAir表示をやめる
        Dim stations = From c In Me.Context.Stations Where c.Id = e.LiveProgram.Station.Id
        If stations.Count > 0 Then
            Dim programs = From p In Me.Context.LivePrograms _
                           Where p.Station.Id = e.LiveProgram.Station.Id

            If programs.Count = 1 AndAlso programs.First.Id = "lv" & e.LiveProgram.Id.ToString Then
                stations.First.IsOnAir = False
            End If

            Try
                CollectionViewSource.GetDefaultView(Me.Context.Stations).Refresh()
            Catch ex As Exception
            End Try
        End If

        Dim deleteTargets = From p In Me.Context.LivePrograms Where p.Id = "lv" & e.LiveProgram.Id.ToString
        If deleteTargets.Count > 0 Then
            Dim alerted = deleteTargets.First.IsAlerted
            Me.Context.LivePrograms.Remove(deleteTargets.First)
            If alerted Then
                ChangeTrayStatus()
            End If
        End If

        ProgramDataGrid.UpdateLayout() ' TODO
    End Sub

    Private Sub LiveNotification_LiveProgramUpdated(ByVal sender As Object, ByVal e As NamaAlert.Model.LiveProgramEventArgs) Handles LiveNotification.LiveProgramUpdated

        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, LiveProgramEventArgs)(AddressOf LiveNotification_LiveProgramUpdated), DispatcherPriority.Background, New Object() {sender, e})
            Exit Sub
        End If

        'Debug.Print("Updated " & e.LiveProgram.Title)

        Dim program = e.LiveProgram
        Dim pid = "lv" & program.Id.ToString
        Dim existPrograms = From p In Me.Context.LivePrograms Where p.Id = pid
        If existPrograms.Count = 0 Then
            Exit Sub
        End If

        ' 情報の更新
        With existPrograms.First
            .VisitorCount = If(program.VisitorCount > .VisitorCount, program.VisitorCount, .VisitorCount)
            .CommentCount = If(program.CommentCount > .CommentCount, program.CommentCount, .CommentCount)
            .Caster = If(program.CasterName <> "", program.CasterName, .Caster)
            .CrawlDateTime = program.CrawlingDateTime

            .IsMemberOnly = If(program.IsMemberOnly, True, .IsMemberOnly)
            .IsFace = If(program.IsFace, True, .IsFace)
            .Category = If(program.OfficialTag <> "", program.OfficialTag, .Category)

            .StartDateTime = program.StartDateTime ' API通知の生放送は通知時刻になっているため
        End With

        ' Station
        Dim isOnAir As Boolean
        Dim sid = program.Station.Id
        Dim existStation = From s In Me.Context.Stations Where s.Id = sid
        If existStation.Count > 0 _
            AndAlso existStation.First.RecentAirdate <= program.StartDateTime.AddMinutes(5) Then
            ' MEMO RecentAirdate には、LiveCommentServer により クライアントPCの時刻が設定されている場合がある

            ' 情報の更新
            With existStation.First
                .Name = program.Station.Name
                .RecentAirdate = program.StartDateTime
                .RecentTitle = program.Title
                .IsOnAir = True
            End With
            isOnAir = True

            Try
                CollectionViewSource.GetDefaultView(Me.Context.Stations).Refresh()
            Catch ex As Exception
            End Try
        End If

        ' 復帰時 IsAlert 設定 仮対応
        If AlertManager.AlertedIds.Contains(pid) Then
            existPrograms.First.IsAlerted = True
        End If

        Me.Resort(existPrograms.First)

    End Sub

#End Region

#Region "Live Programs Crawling"

    'Private LiveProgramIds As New List(Of String)

    Private Sub Resort(ByVal program As LiveProgram)

        ' MEMO ソート対策
        Dim descs = From d In ProgramDataGrid.Items.SortDescriptions Select d.PropertyName

        If descs.Contains("VisitorCount") OrElse descs.Contains("CommentCount") OrElse _
           descs.Contains("VisitorCountPerMinute") OrElse descs.Contains("CommentCountPerMinute") OrElse _
           descs.Contains("IsAlerted") Then

            Dim newProgram As New LiveProgram With { _
                .Id = program.Id, _
                .Title = program.Title, _
                .Content = program.Content, _
                .StartDateTime = program.StartDateTime, _
                .VisitorCount = program.VisitorCount, _
                .CommentCount = program.CommentCount, _
                .IsMemberOnly = program.IsMemberOnly, _
                .IsAlerted = program.IsAlerted, _
                .Station = program.Station, _
                .CrawlDateTime = program.CrawlDateTime, _
                .Caster = program.Caster, _
                .Category = program.Category, _
                .IsFace = program.IsFace}

            Dim selectedIndex = ProgramDataGrid.SelectedIndex
            Dim selectedItem = DirectCast(ProgramDataGrid.SelectedItem, LiveProgram)
            Dim isSelected As Boolean
            If selectedItem IsNot Nothing AndAlso selectedItem.Id = newProgram.Id Then
                isSelected = True
            End If

            Me.Context.LivePrograms.Remove(program)
            Me.Context.LivePrograms.Add(newProgram)

            If Me.Context.LivePrograms.Contains(selectedItem) Then
                ProgramDataGrid.SelectedItem = selectedItem
            ElseIf isSelected Then
                ProgramDataGrid.SelectedItem = newProgram
            ElseIf selectedIndex >= 0 Then
                ProgramDataGrid.SelectedIndex = selectedIndex
            End If
        End If

    End Sub


#End Region

#Region "Commands"

    Private Sub AddStationsCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
        For Each program As LiveProgram In ProgramDataGrid.SelectedItems

            Dim id = program.Station.Id
            If (From c In Me.Context.Stations Where c.Id = id).Count = 0 Then
                Dim station = program.Station

                With station
                    .RecentAirdate = program.StartDateTime
                    .RecentTitle = program.Title
                    .AlertByWindow = Settings.DefaultAlert.AlertByWindow
                End With

                program.IsAlerted = True
                Me.Context.Stations.Add(station) ' 通知対象に追加

            End If
        Next

        DirectCast(ProgramDataGrid.ItemsSource, ListCollectionView).Refresh()
        StationTabItem.IsSelected = True
        Me.Save()
        ChangeTrayStatus()
    End Sub

    Private Sub ForDataGridRowsCommand_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)

        If e.Source Is ProgramDataGrid Then
            If ProgramDataGrid.SelectedItems.Count > 0 Then
                e.CanExecute = True
            End If
        ElseIf e.Source Is StationDataGrid Then
            If StationDataGrid.SelectedItems.Count > 0 Then
                e.CanExecute = True
            End If
        ElseIf e.Source Is KeywordDataGrid Then
            If KeywordDataGrid.SelectedItems.Count > 0 Then
                e.CanExecute = True
            End If
        End If

    End Sub

    Private Sub ForOnlyDataGridRowCommand_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)

        If e.Source Is ProgramDataGrid Then
            If ProgramDataGrid.SelectedItems.Count = 1 Then
                e.CanExecute = True
            End If
        ElseIf e.Source Is StationDataGrid Then
            If StationDataGrid.SelectedItems.Count = 1 Then
                e.CanExecute = True
            End If
        End If

    End Sub

    Private Sub OpenUrisCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        Dim parameter = CStr(e.Parameter)

        Dim ps = New List(Of LiveProgram)
        Dim uris = New List(Of Uri)

        Select Case parameter
            Case "program"
                For Each p As LiveProgram In ProgramDataGrid.SelectedItems
                    ps.Add(p)
                Next
            Case "liveprogram"
                For Each s As Station In StationDataGrid.SelectedItems
                    If s.IsOnAir Then
                        Dim id = s.Id
                        Dim programs = Me.Context.LivePrograms.Where(Function(p) p.Station.Id = id)
                        If programs.Count > 0 Then
                            ps.Add(programs.First)
                        End If
                    End If
                Next
            Case "station"
                For Each s As Station In StationDataGrid.SelectedItems
                    uris.Add(s.Uri)
                Next
            Case Else
                Exit Sub
        End Select

        If ps.Count >= 5 Then
            If CenterMessageBox.Show(Me, "5個以上選択されています。開きますか？", My.Resources.ApplicationName, MessageBoxButton.YesNo, MessageBoxImage.Information, MessageBoxResult.No) = MessageBoxResult.No Then
                Exit Sub
            End If
        End If

        For Each p In ps
            BrowserManager.OpenUri(p)
        Next
        For Each u In uris
            BrowserManager.OpenUri(u)
        Next

    End Sub

    Private Sub CopyUriCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        Dim parameter = CStr(e.Parameter)

        Dim uri As Uri = Nothing
        Select Case parameter
            Case "program"
                uri = DirectCast(ProgramDataGrid.SelectedItem, LiveProgram).Uri
            Case "liveprogram"
                For Each s As Station In StationDataGrid.SelectedItems
                    If s.IsOnAir Then
                        Dim id = s.Id
                        Dim programs = Me.Context.LivePrograms.Where(Function(p) p.Station.Id = id)
                        If programs.Count > 0 Then
                            uri = programs.First.Uri
                            Exit For
                        End If
                    End If
                Next
            Case "station"
                uri = DirectCast(StationDataGrid.SelectedItem, Station).Uri
            Case Else
                Exit Sub
        End Select

        Try
            Windows.Clipboard.SetText(uri.ToString)
        Catch ex As Exception
            ' Ignore
        End Try
    End Sub

    Private Sub ShowAlertSettingsCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        Dim list = New List(Of AlertTarget)

        If StationTabItem.IsSelected Then

            If StationDataGrid.SelectedItems.Count > 0 Then
                For Each s As Station In StationDataGrid.SelectedItems
                    list.Add(s)
                Next
            End If

        ElseIf KeywordTabItem.IsSelected Then

            If KeywordDataGrid.SelectedItems.Count > 0 Then
                For Each k In KeywordDataGrid.SelectedItems
                    If TypeOf k Is Keyword Then
                        list.Add(DirectCast(k, Keyword))
                    End If
                Next
            End If
        End If

        Dim window = New AlertSettingsWindow(list, Settings) With { _
            .Owner = Me}
        If window.ShowDialog Then

        End If
    End Sub

    Private Sub DeleteStationsCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        If CenterMessageBox.Show(Me, "削除します。よろしいですか？", My.Resources.ApplicationName, MessageBoxButton.YesNo, MessageBoxImage.Information, MessageBoxResult.No) = MessageBoxResult.No Then
            Exit Sub
        End If

        For i = StationDataGrid.SelectedItems.Count - 1 To 0 Step -1
            Try
                Dim station = DirectCast(StationDataGrid.SelectedItems(i), Station)
                Me.Context.Stations.Remove(station)

                ' アラート表示解除
                Dim id = station.Id
                Dim programs = From p In Me.Context.LivePrograms Where p.Station.Id = id
                For Each p In programs
                    p.IsAlerted = False
                Next
            Catch ex As Exception
                ' Ignore
                ' TODO
            End Try
        Next
        Me.Save()

    End Sub

    Private Sub DeleteKeywordsCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        If CenterMessageBox.Show(Me, "削除します。よろしいですか？", My.Resources.ApplicationName, MessageBoxButton.YesNo, MessageBoxImage.Information, MessageBoxResult.No) = MessageBoxResult.No Then
            Exit Sub
        End If

        For i = KeywordDataGrid.SelectedItems.Count - 1 To 0 Step -1
            Try
                Me.Context.Keywords.Remove(DirectCast(KeywordDataGrid.SelectedItems(i), Keyword))
            Catch ex As Exception
                ' Ignore
                ' TODO
            End Try
        Next
        Me.Save()
    End Sub

    Private Sub DeleteKeywordsCommand_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)

        If KeywordDataGrid.Items.Count > 1 Then

            For Each item In KeywordDataGrid.SelectedItems
                If Not TypeOf item Is Keyword Then
                    Exit Sub
                End If
            Next

            e.CanExecute = True
        End If

    End Sub

#End Region

#Region "絞り込み"
    Private WithEvents FilterTimer As New DispatcherTimer

    Private Sub FilterTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles FilterTimer.Tick
        DirectCast(ProgramDataGrid.ItemsSource, ListCollectionView).Refresh()
        FilterTimer.Stop()
    End Sub

    Private Function FilterProgram(ByVal sender As Object) As Boolean
        'Try

        '    Dim program = DirectCast(sender, LiveProgram)

        '    Dim words = FilterTextBox.Text.ToLower.Split(New Char() {" "c, "　"c}, StringSplitOptions.RemoveEmptyEntries)
        '    If words.Length = 0 Then
        '        Return True
        '    End If

        '    For Each w In words
        '        If program.Title.ToLower.Contains(w) OrElse _
        '           program.Station.Name.ToLower.Contains(w) OrElse _
        '           program.Content.ToLower.Contains(w) OrElse _
        '           CStr(program.Caster).ToLower.Contains(w) Then
        '            Continue For
        '        Else
        '            Return False
        '        End If
        '    Next
        '    Return True
        'Catch ex As Exception
        'End Try

        ' Original codes by mu-lp. 2009-03-23.
        ' Ref: http://namaalert.codeplex.com/Thread/View.aspx?ThreadId=50978

        Dim program = DirectCast(sender, LiveProgram)

        Dim andWords = New List(Of String)
        Dim orWords = New List(Of String)
        Dim notWords = New List(Of String)
        Dim targetList As IList(Of String) = andWords

        Dim filterText = FilterTextBox.Text.ToUpperAsciiAlphanumeric & " " '末尾にスペースを足すのは「とある」おまじない
        Dim word As String = ""

        ' トークン分解 ループ
        For Each c In filterText
            If " 　&|-".Contains(c) AndAlso word.Length > 0 Then
                targetList.Add(word)
                word = ""
            End If

            Select Case c
                Case " "c, "　"c, "&"c
                    targetList = andWords
                Case "|"c
                    targetList = orWords
                Case "-"c
                    targetList = notWords
                Case Else
                    word &= c
            End Select
        Next

        ' 絞り込みワードが0個ならtrue返して関数終了
        If andWords.Count = 0 AndAlso orWords.Count = 0 AndAlso notWords.Count = 0 Then
            Return True
        End If

        Dim title = program.Title.ToUpperAsciiAlphanumeric
        Dim stationName = program.Station.Name.ToUpperAsciiAlphanumeric
        Dim content = program.Content.ToUpperAsciiAlphanumeric
        Dim caster = If(program.Caster, "").ToUpperAsciiAlphanumeric

        ' NOTキーワードが含まれているか判別
        For Each w In notWords
            If title.Contains(w) OrElse _
               stationName.Contains(w) OrElse _
               content.Contains(w) OrElse _
               caster.Contains(w) Then

                Return False
            End If
        Next

        ' ORキーワードが含まれているか判別
        For Each w In orWords
            If title.Contains(w) OrElse _
               stationName.Contains(w) OrElse _
               content.Contains(w) OrElse _
               caster.Contains(w) Then
                Return True
            End If
        Next

        ' ANDキーワード全て含まれているか判別
        If andWords.Count = 0 Then
            Return False
        End If

        For Each w In andWords
            If title.Contains(w) OrElse _
               stationName.Contains(w) OrElse _
               content.Contains(w) OrElse _
               caster.Contains(w) Then
                Continue For
            Else
                Return False
            End If
        Next

        Return True

    End Function

    Private Sub FilterTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs)
        If Not TypeOf sender Is Controls.TextBox Then
            Exit Sub
        End If

        FilterTimer.Interval = New TimeSpan(0, 0, 1)
        FilterTimer.Start()
    End Sub

    Private Sub FilterProgramsCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
        FilterTextBox.Focus()
    End Sub

#End Region

#Region "放送局追加"


    Private WithEvents StationTimer As New DispatcherTimer
    Private AddTarget As Station

    Private Sub StationTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles StationTimer.Tick
        AddTarget = Nothing

        StationTimer.Stop()
        Dim text = StationTextBox.Text.ToLower.Trim
        If text = "" Then
            Exit Sub
        End If

        Dim match = Regex.Match(text, "(?<id>(co|ch)\d+)")
        If Not match.Success Then
            Exit Sub
        End If

        AddStatusBarText("")
        Crawler.GetStationAsync(match.Groups("id").Value)

    End Sub

    Private Sub StationTextBox_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs)
        If Not TypeOf sender Is Controls.TextBox Then
            Exit Sub
        End If

        StationTimer.Interval = New TimeSpan(0, 0, 1)
        StationTimer.Start()
    End Sub

    Private Sub StationGettingCompleted(ByVal sender As Object, ByVal e As StationCrawlingEventAges) Handles Crawler.StationGettingCompleted
        If e.Station Is Nothing Then
            Exit Sub
        End If

        AddTarget = e.Station
        AddTarget.AlertByWindow = True ' TODO 既定の設定を使う
        AddStatusBarText(e.Station.Name & "を追加します")

    End Sub

    Private Sub AddSearchedStation()
        If AddTarget IsNot Nothing Then
            Dim id = AddTarget.Id
            If (From s In Me.Context.Stations Where s.Id = id).Count > 0 Then
                CenterMessageBox.Show(Me, "既に追加されています。", My.Resources.ApplicationName, MessageBoxButton.OK, MessageBoxImage.Information)
            Else
                Me.Context.Stations.Add(AddTarget)
            End If
        End If
        AddTarget = Nothing
        AddStatusBarText("")
        StationTextBox.Text = ""
    End Sub

    Private Sub AddStationButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        AddSearchedStation()
    End Sub

    Private Sub StationTextBox_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs)
        If e.Key = Key.Enter AndAlso AddTarget IsNot Nothing Then
            AddSearchedStation()
        End If
    End Sub

#End Region

#Region "Status Bar"

    Public Sub AddStatusBarText(ByVal text As String)

        If Dispatcher.Thread IsNot System.Threading.Thread.CurrentThread Then
            Dispatcher.Invoke(Windows.Threading.DispatcherPriority.Normal, _
                              New Action(Of String)(AddressOf AddStatusBarText), text, New Object() {})

            Exit Sub
        End If

        Me.Context.StatusBarText = text
    End Sub

#End Region

#Region "Live Program DataGrid"

    Private Sub ProgramDataGird_MouseDoubleClick(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
        Dim d = DirectCast(e.OriginalSource, DependencyObject)
        Do While d IsNot Nothing AndAlso _
            Not TypeOf d Is Microsoft.Windows.Controls.DataGridCell AndAlso _
            Not TypeOf d Is DataGridColumnHeader
            d = VisualTreeHelper.GetParent(d)
        Loop
        If d Is Nothing Then
            Exit Sub
        ElseIf TypeOf d Is DataGridColumnHeader Then
            ' DataGridColumnHeader !
        ElseIf TypeOf d Is Microsoft.Windows.Controls.DataGridCell Then
            ' DataGridCell !
            If e.ChangedButton <> MouseButton.Left Then
                Exit Sub
            End If
            If ProgramDataGrid.SelectedItems.Count = 1 Then
                BrowserManager.OpenUri(DirectCast(ProgramDataGrid.SelectedItem, LiveProgram))
            End If

        End If
    End Sub


    Private Sub ProgramDataGrid_RowDetailsVisibilityChanged(ByVal sender As System.Object, ByVal e As Microsoft.Windows.Controls.DataGridRowDetailsEventArgs)
        Try
            If TypeOf e.Row.Item Is LiveProgram Then
                DirectCast(e.Row.Item, LiveProgram).Station.NeedDisplay = True
            End If
        Catch ex As Exception

        End Try

    End Sub

#End Region

#Region "Station DataGrid"

    Private Sub StationDataGird_MouseDoubleClick(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
        Dim d = DirectCast(e.OriginalSource, DependencyObject)
        Do While d IsNot Nothing AndAlso _
            Not TypeOf d Is Microsoft.Windows.Controls.DataGridCell AndAlso _
            Not TypeOf d Is DataGridColumnHeader
            d = VisualTreeHelper.GetParent(d)
        Loop
        If d Is Nothing Then
            Exit Sub
        ElseIf TypeOf d Is DataGridColumnHeader Then
            ' DataGridColumnHeader !
        ElseIf TypeOf d Is Microsoft.Windows.Controls.DataGridCell Then
            ' DataGridCell !
            If e.ChangedButton <> MouseButton.Left Then
                Exit Sub
            End If

            If StationDataGrid.SelectedItems.Count = 1 Then
                Dim station = DirectCast(StationDataGrid.SelectedItem, Station)
                If Not station.IsOnAir Then
                    BrowserManager.OpenUri(station.Uri)
                Else
                    Dim id = station.Id
                    Dim programs = From p In Me.Context.LivePrograms Where p.Station.Id = id
                    If programs.Count > 0 Then
                        BrowserManager.OpenUri(programs.First)
                    Else
                        BrowserManager.OpenUri(station.Uri)
                    End If
                End If
            End If

        End If
    End Sub

#End Region

#Region "DataGrid Single Click"

    Private Shared Function FindVisualParent(Of T As UIElement)(ByVal element As UIElement) As T
        Dim parent As UIElement = element
        While parent IsNot Nothing
            Dim correctlyTyped As T = TryCast(parent, T)
            If correctlyTyped IsNot Nothing Then
                Return correctlyTyped
            End If

            parent = TryCast(VisualTreeHelper.GetParent(parent), UIElement)
        End While
        Return Nothing
    End Function

    Private Sub DataGridCell_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
        Dim cell As Microsoft.Windows.Controls.DataGridCell = TryCast(sender, Microsoft.Windows.Controls.DataGridCell)
        If cell IsNot Nothing AndAlso Not cell.IsEditing AndAlso Not cell.IsReadOnly Then
            If Not cell.IsFocused Then
                cell.Focus()
            End If
            Dim dataGrid As Microsoft.Windows.Controls.DataGrid = FindVisualParent(Of Microsoft.Windows.Controls.DataGrid)(cell)
            If dataGrid IsNot Nothing Then
                If dataGrid.SelectionUnit <> DataGridSelectionUnit.FullRow Then
                    If Not cell.IsSelected Then
                        cell.IsSelected = True
                    End If
                Else
                    Dim row As DataGridRow = FindVisualParent(Of DataGridRow)(cell)
                    If row IsNot Nothing AndAlso Not row.IsSelected Then
                        row.IsSelected = True
                    End If
                End If
            End If
        End If
    End Sub

#End Region



    Private Sub GeneralSettingsButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Dim window = New GeneralSettingsWindow(Settings.General) With {.Owner = Me}
        If window.ShowDialog Then

        End If
    End Sub

    Private ReadOnly Property UserDataPath() As String
        Get
            Return System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Co9320\NamaAlert\1.0.0.0")
        End Get
    End Property

    Public Sub Load()

        Try

            Dim settingsFile = System.IO.Path.Combine(Me.UserDataPath, My.Resources.UserSettingsFile)
            If System.IO.File.Exists(settingsFile) Then
                Dim document = XDocument.Load(settingsFile)

                'TODOチェック方法
                If document.<settings>.Elements.Count > 0 Then
                    Settings.Deserialize(document.<settings>.Elements.First)

                    'If document.<settings>.<UI>.<MainWindow>.Count > 0 Then
                    '    Dim el = document.<settings>.<UI>.<MainWindow>.First

                    '    If el.<Width>.Value IsNot Nothing Then
                    '        Me.Width = CInt(el.<Width>.Value)
                    '    End If
                    '    If el.<Height>.Value IsNot Nothing Then
                    '        Me.Height = CInt(el.<Height>.Value)
                    '    End If

                    '    If el.<Left>.Value IsNot Nothing Then
                    '        Me.Left = CInt(el.<Left>.Value)
                    '    End If
                    '    If el.<Top>.Value IsNot Nothing Then
                    '        Me.Top = CInt(el.<Top>.Value)
                    '    End If
                    'End If

                    If document.<settings>.<UI>.<ProgramDataGrid>.Count > 0 Then
                        ProgramDataGrid.Deserialize(document.<settings>.<UI>.<ProgramDataGrid>.First)
                    End If
                    If document.<settings>.<UI>.<StationDataGrid>.Count > 0 Then
                        StationDataGrid.Deserialize(document.<settings>.<UI>.<StationDataGrid>.First)
                    End If
                    If document.<settings>.<UI>.<KeywordDataGrid>.Count > 0 Then
                        KeywordDataGrid.Deserialize(document.<settings>.<UI>.<KeywordDataGrid>.First)
                    End If

                End If


            End If

            Dim ngStationsFile = System.IO.Path.Combine(Me.UserDataPath, "NgStations.txt")
            If System.IO.File.Exists(ngStationsFile) Then
                Dim lines = My.Computer.FileSystem.ReadAllText(ngStationsFile, System.Text.Encoding.UTF8).Split(New String() {vbCr, vbLf}, StringSplitOptions.RemoveEmptyEntries)
                For Each l In lines
                    NgStationIds.Add(l.Trim)
                Next
            End If



        Catch ex As Exception

        End Try

        ' TODO
        For Each s In Settings.Stations
            s.IsOnAir = False
        Next
    End Sub

    Public Sub Save()

        If Not System.IO.Directory.Exists(Me.UserDataPath) Then
            My.Computer.FileSystem.CreateDirectory(Me.UserDataPath)
        End If

        Dim rect As Rect
        If Me.WindowState = Windows.WindowState.Normal Then
            rect = New Rect(Me.Left, Me.Top, Me.ActualWidth, Me.ActualHeight)
        Else
            rect = WindowRect
        End If

        Dim document = <?xml version="1.0" encoding="utf-8"?>
                       <settings>
                           <%= Settings.Serialize %>
                           <UI>
                               <%= ProgramDataGrid.Serialize %>
                               <%= StationDataGrid.Serialize %>
                               <%= KeywordDataGrid.Serialize %>
                               <MainWindow>
                                   <Width><%= rect.Width %></Width>
                                   <Height><%= rect.Height %></Height>
                                   <Left><%= rect.Left %></Left>
                                   <Top><%= rect.Top %></Top>
                               </MainWindow>
                           </UI>
                       </settings>

        Dim settingsFile = System.IO.Path.Combine(Me.UserDataPath, My.Resources.UserSettingsFile)
        document.Save(settingsFile)

    End Sub


#Region "TaskTray"

    Private Sub InitializeTray()

        TrayIcon.Icon = My.Resources.TrayIconNormal
        TrayIcon.Text = My.Resources.ApplicationName
        TrayIcon.Visible = True
        TrayIcon.ContextMenu = New Forms.ContextMenu

        TrayIcon.ContextMenu.MenuItems.Add("開く", AddressOf TrayOpenMenuItem_Click)
        TrayIcon.ContextMenu.MenuItems.Add("-")
        TrayIcon.ContextMenu.MenuItems.Add("終了", AddressOf TrayCloseMenuItem_Click)

        '
        BalloonManager.SetNotifyIcon(TrayIcon)

    End Sub

    Private Sub ChangeTrayStatus()

        Dim menuPrograms = From i In TrayIcon.ContextMenu.MenuItems Where TypeOf DirectCast(i, Forms.MenuItem).Tag Is LiveProgram Select DirectCast(i, Forms.MenuItem).Tag
        Dim alertedProgmras = From p In Me.Context.LivePrograms Where p.IsAlerted Order By p.StartDateTime Descending

        Dim isChanged As Boolean = False
        If menuPrograms.Count <> alertedProgmras.Count Then
            isChanged = True
        Else
            For Each mp In menuPrograms
                Dim id = DirectCast(mp, LiveProgram).Id
                If (From ap In alertedProgmras Where ap.Id = id).Count <> 1 Then
                    isChanged = True
                    Exit For
                End If
            Next
        End If

        If Not isChanged Then
            Exit Sub
        End If


        Debug.WriteLine("alertedProgmras.Count = " & alertedProgmras.Count.ToString)
        TrayIcon.ContextMenu.MenuItems.Clear()
        If alertedProgmras.Count > 0 Then
            TrayIcon.Icon = My.Resources.TrayIconAlerted
            For Each p In alertedProgmras
                Dim item = New Forms.MenuItem(p.Title, AddressOf TrayMenuItem_Click)
                item.Tag = p
                TrayIcon.ContextMenu.MenuItems.Add(item)
            Next
            TrayIcon.ContextMenu.MenuItems.Add("-")

        Else
            TrayIcon.Icon = My.Resources.TrayIconNormal
        End If

        ' 通知数更新
        Try
            Me.Context.AlertedProgramsCount = alertedProgmras.Count
            Me.Context.AlertedStationsCount = (From s In Me.Context.Stations Where s.IsOnAir).Count
        Catch ex As Exception

        End Try

        TrayIcon.ContextMenu.MenuItems.Add("開く", AddressOf TrayOpenMenuItem_Click)

        TrayIcon.ContextMenu.MenuItems.Add("-")
        TrayIcon.ContextMenu.MenuItems.Add("終了", AddressOf TrayCloseMenuItem_Click)


    End Sub



    Private Sub TrayMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs)
        If TypeOf sender Is Forms.MenuItem Then
            Dim item = DirectCast(sender, Forms.MenuItem)
            If TypeOf item.Tag Is LiveProgram Then
                BrowserManager.OpenUri(DirectCast(item.Tag, LiveProgram))
            End If
        End If
    End Sub

    Private Sub ShowWindow()
        Me.Show()
        Me.Visibility = Windows.Visibility.Visible
        If Me.WindowState = Windows.WindowState.Minimized Then
            Me.WindowState = Windows.WindowState.Normal
        End If
        Me.Activate()
        Me.Context.Settings.General.IsInTray = False

        RestartLiveNotification()
    End Sub

    Private Sub TrayOpenMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs)
        ShowWindow()
    End Sub

    Private Sub TrayCloseMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs)
        ' TODO 複数個所の終了処理を統一
        IsClosed = True
        TrayIcon.Visible = False
        Me.Save()
        Me.Close()
    End Sub

    Private Sub TrayIcon_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TrayIcon.MouseDoubleClick
        If e.Button = MouseButtons.Left Then
            ShowWindow()
        End If
    End Sub

#End Region

#Region "Timer"

    Private WithEvents RamenTimer As New DispatcherTimer
    Private Sub SetTimerCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        RamenTimer.Stop()
        Dim i = CInt(e.Parameter)
        If i = 0 Then
            Me.Context.RunningTimer = False
            Exit Sub
        End If

        Me.Context.RunningTimer = True
        RamenTimer.Interval = New TimeSpan(0, i, 0)
        RamenTimer.Start()

    End Sub

    Private Sub RamenTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles RamenTimer.Tick
        RamenTimer.Stop()
        Me.Context.RunningTimer = False

        Dim window = New AlertWindow(New AlertWindowDataContext With { _
                        .Title = "こなタイマー", _
                        .Icon = New BitmapImage(New Uri("http://icon.nicovideo.jp/community/co15905.jpg")), _
                        .Content = "あぷぷぇ！", _
                        .FooterLeft = RamenTimer.Interval.Minutes.ToString & "分経過", _
                        .FooterRight = "(残り0分)", _
                        .Uri = New Uri("http://ch.nicovideo.jp/community/co15905")})
        window.Show()
    End Sub
#End Region


    Private Function GetVersionDateTime(ByVal Version As String) As DateTime
        Dim y = Version.Substring(0, 2)
        Dim m = Version.Substring(3, 2)
        Dim d = Version.Substring(6, 2)

        Dim clientVersion As DateTime
        Dim match = System.Text.RegularExpressions.Regex.Match(Version, "^(?<y>\d+)\-(?<m>\d+)\-(?<d>\d+)((\.)(?<n>\d+))?$")
        If match.Success Then
            clientVersion = New DateTime(2000 + CInt(match.Groups("y").Value), _
                                   CInt(match.Groups("m").Value), _
                                   CInt(match.Groups("d").Value))
            If match.Groups("n").Value <> "" Then
                clientVersion = clientVersion.AddHours(CInt(match.Groups("n").Value))
            End If
        End If

        Return clientVersion
    End Function

    Private Sub ConfigClient_GetSettingsCompleted(ByVal sender As Object, ByVal e As ServiceReference.GetSettingsCompletedEventArgs) Handles ConfigClient.GetSettingsCompleted
        Try

            For Each kv In e.Result
                If kv.Key = "LatestVersion" Then
                    If GetVersionDateTime(My.Resources.Version) < GetVersionDateTime(kv.Value) Then
                        AddStatusBarText(My.Resources.ApplicationName & " Ver. " & My.Resources.Version & " (最新は Ver. " & kv.Value & "です。更新してください。)")
                    End If
                    Continue For
                End If

                Try
                    My.Settings(kv.Key) = kv.Value
                Catch ex As Exception
                End Try
            Next
            My.Settings.Save()
            ConfigClient.Close()

        Catch ex As Exception

        End Try
        'Crawler.CrawlLivePrograms(0)
        InitializeNotification()
    End Sub

    Private Sub MenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        Try
            Process.Start(Me.UserDataPath)
        Catch ex As Exception

        End Try
    End Sub


    Private Sub RegisterStartupMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

    End Sub

    Private Sub ContextMenu_ContextMenuOpening(ByVal sender As System.Object, ByVal e As System.Windows.Controls.ContextMenuEventArgs)
        ' Startup
        ' System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly.Location)
        ' C:\Users\Owner\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\NamaAlert project\ニコ生アラート
        ' ニコ生アラート（βa）
        Dim s = System.Environment.GetFolderPath(Environment.SpecialFolder.StartMenu)

        Dim appRef = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), _
                                            "Programs\NamaAlert project\ニコ生アラート\ニコ生アラート（βa）.appref-ms")

        If System.IO.File.Exists(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.Startup), _
                                                        "ニコ生アラート（βa）.appref-ms")) Then

        End If

    End Sub

    Private ChangingModeDateTime As DateTime
    Private WithEvents NotificationTimer As New DispatcherTimer

    Private Sub NotificationTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles NotificationTimer.Tick
        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, EventArgs)(AddressOf NotificationTimer_Tick), DispatcherPriority.Send, New Object() {sender, e})
            Exit Sub
        End If

        If LiveNotification Is Nothing Then
            Exit Sub
        End If

        Debug.WriteLine("NotificationTimer_Tick")
        NotificationTimer.Stop()

        ' Change LiveNotification mode
        Try
            If Me.Context.Keywords.Count = 0 Then
                LiveNotification.ChangeCrawlingMode(CrawlingMode.Sleep)
            Else
                LiveNotification.ChangeCrawlingMode(CrawlingMode.NormalWithoutClosedNotification)
            End If

            SleepingTimer.Interval = New TimeSpan(0, 0, 30)
            SleepingTimer.Start()
        Catch ex As Exception

        End Try
        ChangingModeDateTime = Now
    End Sub

    Private WithEvents SleepingTimer As New DispatcherTimer

    Private Sub SleepingTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles SleepingTimer.Tick
        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of Object, EventArgs)(AddressOf SleepingTimer_Tick), DispatcherPriority.Send, New Object() {sender, e})
            Exit Sub
        End If

        Try
            If LiveNotification.CrawlingMode <> CrawlingMode.Normal Then
                For i = Me.Context.LivePrograms.Count - 1 To 0 Step -1
                    Dim p = Me.Context.LivePrograms(i)

                    If p.StartDateTime.AddMinutes(30) < Now Then
                        Me.Context.LivePrograms.Remove(p)
                    End If

                    'If p.StartDateTime.AddHours(6) < Now Then
                    '    Me.Context.LivePrograms.Remove(p)
                    'ElseIf p.StartDateTime.AddMinutes(30) < Now Then
                    '    p.IsAlerted = False
                    'End If
                Next

                'Dim ps = From p In Me.Context.LivePrograms Where p.StartDateTime.AddMinutes(30) < Now
                'Dim deleted = CBool(ps.Count > 0)
                'For i = ps.Count - 1 To 0 Step -1
                '    Debug.WriteLine(ps(i).Title & " " & ps(i).StartDateTime.ToString)
                '    ps(i).IsAlerted = False
                '    'Me.Context.LivePrograms.Remove(ps(i))
                'Next
                ChangeTrayStatus()
            End If
        Catch ex As Exception

        End Try

    End Sub

    Private Sub RestartLiveNotification()
        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(AddressOf RestartLiveNotification), DispatcherPriority.Send, Nothing)
            Exit Sub
        End If

        NotificationTimer.Stop()
        SleepingTimer.Stop()

        If LiveNotification Is Nothing OrElse LiveNotification.CrawlingMode = CrawlingMode.Normal Then
            Exit Sub
        End If


        Try
            LiveNotification.Dispose()
        Catch ex As Exception
        End Try

        Dim stations = From s In Me.Context.Stations Where s.IsOnAir = True
        For Each s In stations
            s.IsOnAir = False
        Next

        LiveNotification = New LiveNotification(My.Settings.UserAgent, My.Settings.LiveProgramsFeedUriFormat, New TimeSpan(0, 0, 3))
        Me.Context.LivePrograms.Clear()
        LiveNotification.Start()

        'If ChangingModeDateTime.AddMinutes(30) < Now Then

        '    Me.Context.LivePrograms.Clear()
        '    LiveNotification.Start()

        'Else
        '    Dim list = New List(Of NamaAlert.Model.LiveProgram)
        '    For Each p In Me.Context.LivePrograms

        '        Dim program = New NamaAlert.Model.LiveProgram With { _
        '            .Id = CInt(p.Id.Substring(2)), _
        '            .Title = p.Title, _
        '            .Caster = p.Caster, _
        '            .ShortContent = p.Content, _
        '            .IsFace = p.IsFace, _
        '            .IsMemberOnly = p.IsMemberOnly, _
        '            .OfficialTag = p.Category, _
        '            .StartDateTime = p.StartDateTime, _
        '            .VisitorCount = p.VisitorCount, _
        '            .CommentCount = p.CommentCount, _
        '            .CrawlingDateTime = p.CrawlDateTime}

        '        With program.Station
        '            .Id = p.Station.Id
        '            .Name = p.Station.Name
        '        End With
        '    Next

        '    LiveNotification.Start(list, False)
        'End If

    End Sub

    Private Sub MainWindow_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged

        If Me.WindowState = Windows.WindowState.Normal Then
            ' ウィンドウ位置とサイズを記憶
            WindowRect = New Rect(Me.Left, Me.Top, Me.ActualWidth, Me.ActualHeight)
        End If

    End Sub

    Private Sub MainWindow_StateChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.StateChanged



        Select Case Me.WindowState
            Case Windows.WindowState.Minimized
                Debug.WriteLine("MainWindow_StateChanged")
                NotificationTimer.Start()
            Case Else
                RestartLiveNotification()
        End Select

    End Sub


#Region "API"

    Public Sub AddFavorite(ByVal stationId As String) Implements NamaAlert.Contract.IApplicationService.AddFavorite
        If Not Dispatcher.CheckAccess Then
            Dispatcher.Invoke(New Action(Of String)(AddressOf AddFavorite), DispatcherPriority.Normal, New Object() {stationId})
            Exit Sub
        End If

        ' 既に追加されている場合
        If (From s In Me.Context.Stations Where s.Id = stationId).Count > 0 Then
            Exit Sub
        End If

        ' 生放送一覧から対象の放送局を検索
        Dim stations = From p In Me.Context.LivePrograms Where p.Station.Id = stationId Select p.Station
        If stations.Count > 0 Then
            Dim station = New Station With { _
                .Id = stationId, _
                .Name = stations.First.Name}
            Me.Context.Stations.Add(station)
            Exit Sub
        Else

            Dim station = GetStation(stationId)
            If station IsNot Nothing Then
                Me.Context.Stations.Add(station)
            End If

        End If


    End Sub

    Private Function GetStation(ByVal id As String) As Station
        Dim staUri As Uri
        Dim pattern As String

        If id.StartsWith("co") Then
            staUri = New Uri(String.Format(My.Settings.CommunityUriFormatForRetrieving, id))
            pattern = My.Settings.CommunityPageRegEx

        ElseIf id.StartsWith("ch") Then
            staUri = New Uri(String.Format(My.Settings.ChannelUriFormatForRetrieving, id))
            pattern = My.Settings.ChannelPageRegEx
        Else
            Return Nothing
        End If

        Dim html = GetContent(staUri)

        Dim match = Regex.Match(html, pattern, RegexOptions.Singleline)
        If match.Success Then
            Dim station = New Station With { _
                .Id = id, _
                .Name = HttpUtility.UrlDecode(match.Groups("name").Value)}
            Return station
        End If

        Return Nothing
    End Function

    Private Function GetContent(ByVal uri As Uri) As String

        Dim request = DirectCast(WebRequest.Create(uri), HttpWebRequest)

        If My.Settings.ProxyUserName <> "" OrElse _
           My.Settings.ProxyPassword <> "" Then
            request.Credentials = New NetworkCredential(My.Settings.ProxyUserName, My.Settings.ProxyPassword)
        End If

        Dim content As String = ""
        Try
            Dim response = request.GetResponse
            Using stream = response.GetResponseStream, _
                  reader = New System.IO.StreamReader(stream, System.Text.Encoding.UTF8)
                content = reader.ReadToEnd
            End Using
        Catch webEx As WebException

        End Try
        Return content

    End Function

    Public Function GetAlertedLivePrograms() As System.Collections.Generic.IList(Of NamaAlert.Model.LiveProgram) Implements NamaAlert.Contract.IApplicationService.GetAlertedLivePrograms
        If Not Dispatcher.CheckAccess Then
            Return DirectCast(Dispatcher.Invoke(New Action(AddressOf GetAlertedLivePrograms), DispatcherPriority.Normal, Nothing), System.Collections.Generic.IList(Of NamaAlert.Model.LiveProgram))
        End If

        Dim list = New List(Of Model.LiveProgram)
        Try
            Dim ps = From p In Me.Context.LivePrograms Where p.IsAlerted
            For Each program In ps

                Dim p = New Model.LiveProgram With { _
                    .CasterName = program.Caster, _
                    .CommentCount = program.CommentCount, _
                    .Content = program.Content, _
                    .CrawlingDateTime = program.CrawlDateTime, _
                    .Id = CInt(program.Id.Substring(2)), _
                    .IsAlerted = program.IsAlerted, _
                    .IsFace = program.IsFace, _
                    .IsMemberOnly = program.IsMemberOnly, _
                    .IsOnAir = True, _
                    .OfficialTag = program.Category, _
                    .Title = program.Title, _
                    .VisitorCount = program.VisitorCount}

                list.Add(p)
            Next
        Catch ex As Exception

        End Try
        Return list

    End Function

    Public Function GetAllLivePrograms() As System.Collections.Generic.IList(Of NamaAlert.Model.LiveProgram) Implements NamaAlert.Contract.IApplicationService.GetAllLivePrograms
        Throw New NotImplementedException
    End Function

    Public Function GetFavoriteStations() As System.Collections.Generic.IList(Of NamaAlert.Model.Station) Implements NamaAlert.Contract.IApplicationService.GetFavoriteStations
        If Not Dispatcher.CheckAccess Then
            Return DirectCast(Dispatcher.Invoke(New Action(AddressOf GetFavoriteStations), DispatcherPriority.Normal, Nothing), System.Collections.Generic.IList(Of NamaAlert.Model.Station))
        End If

        Dim list = New List(Of Model.Station)
        Try
            For Each station In Me.Context.Stations

                Dim s = New Model.Station With { _
                    .Id = station.Id, _
                    .IsAlertTarget = True, _
                    .IconUri = station.IconUri, _
                    .Name = station.Name}
                list.Add(s)
            Next
        Catch ex As Exception

        End Try
        Return list

    End Function

    Public Function GetVoteCountCombination(ByVal percentCombination As System.Collections.Generic.IList(Of Integer)) As System.Collections.Generic.IList(Of Integer) Implements NamaAlert.Contract.IApplicationService.GetVoteCountCombination

        Dim per(3) As Integer
        For i = 0 To 3
            per(i) = If(percentCombination.Count > i, percentCombination(i), 0)
        Next

        Dim count = New List(Of Integer)

        ' 合計が 0%
        If per.Sum = 0 Then
            count.AddRange(per)
            Return count
        End If

        ' 100% がある
        If (From p In per Where p = 1000).Count > 0 Then
            For i = 0 To 3
                count.Add(0)
                If per(i) = 1000 Then
                    count(i) = 1
                End If
            Next
            Return count
        End If


        Try
            Dim req = DirectCast(WebRequest.Create(String.Format("http://xn--cck7azdj6moeu366c.jp/vote/showresult/{0}/{1}/{2}/{3}", per(0), per(1), per(2), per(3))), HttpWebRequest)
            req.UserAgent = My.Settings.UserAgent & " " & My.Resources.Version

            Dim content As String = ""
            Using response = req.GetResponse, _
                  stream = New System.IO.StreamReader(response.GetResponseStream)
                content = stream.ReadToEnd()
            End Using

            Dim doc = XDocument.Parse(content)
            With doc.<VoteShowResultResponse>.<Result>
                count.Add(CInt(.<Count1>.Value))
                count.Add(CInt(.<Count2>.Value))
                count.Add(CInt(.<Count3>.Value))
                count.Add(CInt(.<Count4>.Value))
            End With
            Return count

        Catch ex As Exception
        End Try

        Return count

    End Function

    Public Function IsFavorite(ByVal stationId As String) As Boolean Implements NamaAlert.Contract.IApplicationService.IsFavorite
        Try
            Dim count = (From s In Me.Context.Stations Where s.Id = stationId).Count
            Return count > 0
        Catch ex As Exception
            Debug.Assert(False)
            Return False
        End Try
    End Function

    Public Sub Register(ByVal applicationId As System.Guid, ByVal path As String) Implements NamaAlert.Contract.IApplicationService.Register
        Try
            If applicationId.ToString.ToUpperInvariant = "1C2F8706-9970-4C7A-884E-0BEB9F6CB870" Then
                Me.Context.Settings.General.NiconamaCommentViewerPath = path
            End If
        Catch ex As Exception

        End Try
    End Sub

    Public Sub StartTimer(ByVal id As String, ByVal time As System.TimeSpan) Implements NamaAlert.Contract.IApplicationService.StartTimer
        Throw New NotImplementedException
    End Sub

    Public Sub StopTimer(ByVal id As String) Implements NamaAlert.Contract.IApplicationService.StopTimer
        Throw New NotImplementedException
    End Sub

#End Region

    Private Sub OpenNiconamaCommentViewerCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

        Try
            Dim id = DirectCast(ProgramDataGrid.SelectedItems(0), LiveProgram).Id
            Process.Start(Me.Context.Settings.General.NiconamaCommentViewerPath, id)
        Catch ex As Exception

        End Try

    End Sub


End Class

